﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows.Forms;
namespace HexDisplay
{
    public partial class HexDisplay : Form
    {
        const int BufferSize = 16 * 1024;
        private string displayedFile;
        private FileStream fs;
        //每行显示的字节数，<= 16
        private int lineBytes = 16;
        private int readSize = 0;
        private int filePos = 0;
        private int fileSize = 0;
        //窗口内可以显示的行数，不包括hexHeader
        private const int displayLines = 20;
        private static List<HexDisplay> allHexWin = new List<HexDisplay>();
        private string hexHeader = "Offset   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F \n";
        private byte[] buffer = new byte[BufferSize];
        private uint charValue = 0;
        private bool haveChange = false;
        public FileStream OpenedFile
        {
            get
            {
                return fs;
            }
        }

        public HexDisplay()
        {
            InitializeComponent();
            strDisplayArea.HideSelection = false;
            displayBar.Maximum = 1000 + displayBar.LargeChange;
            hexDisplayArea.MouseWheel += DealMouseWheel;
            strDisplayArea.MouseWheel += DealMouseWheel;
            allHexWin.Add(this);
            contextMenu.Items["close"].Available = false;
            contextMenu.Items["jump"].Available = false;
        }

        public void DisplayFileAtOffset(int off)
        {
            if(fileSize == 0 || off < 0 || off >= fileSize)
            {
                return;
            }
            off &= ~(lineBytes - 1);
            int i, nDisplay = displayLines * lineBytes;
            int startIndex;
            hexDisplayArea.Text = hexHeader;
            strDisplayArea.Text = "0123456789ABCDEF\n";
            string s_hex = "";
            if(off >= filePos && off + displayLines * lineBytes < filePos + readSize)
            {
                startIndex = off - filePos;
                int length = (filePos + readSize - off);
                if(length < nDisplay)
                {
                    nDisplay = length;
                }
            }
            else
            {
                startIndex = 0;
                fs.Position = off;
                filePos = off;
                readSize = fs.Read(buffer, 0, buffer.Length);
                if(readSize < nDisplay)
                {
                    nDisplay = readSize;
                }
            }
            int j = 0;
            char[] charBuff = new char[nDisplay + nDisplay / lineBytes];
            for(i = startIndex; i < startIndex + nDisplay; i++)
            {
                if(i % lineBytes == 0)
                {
                    s_hex += (filePos + i).ToString("x8") + ":";
                }
                s_hex += buffer[i].ToString("x2") + " ";
                charBuff[j++] = (char)buffer[i];
                if(charBuff[j - 1] < ' ' || charBuff[j - 1] > '~')
                {
                    charBuff[j - 1] = '.';
                }
                if(i % lineBytes == (lineBytes - 1))
                {
                    s_hex += "\n";
                    charBuff[j++] = '\n';
                }
            }
            hexDisplayArea.Text += s_hex;
            strDisplayArea.Text += new string(charBuff, 0, j);
        }

        private void DragNewFileEnter(object sender, DragEventArgs e)
        {
            DataObject data = (DataObject)e.Data;
            if(data.ContainsFileDropList())
            {
                StringCollection fileList = data.GetFileDropList();
                if(IsDifferentFile(fileList[0]))
                {
                    InitNewFile(fileList[0], false);
                }
            }
        }

        private void CloseFile()
        {
            if(fs != null)
            {
                fs.Dispose();
                fs = null;
                displayedFile = "";
                contextMenu.Items["close"].Available = false;
                contextMenu.Items["jump"].Available = false;
            }
        }

        private void HexScroll(object sender, EventArgs e)
        {
            if(fs == null || fileSize == 0)
            {
                return;
            }
            VScrollBar vs = (VScrollBar)sender;
            //单击控件上的小箭头vs.Value无法达到最大值，
            //只能到MAX - LargeChange + SmallChange
            int pos = (int)(1L * vs.Value * fileSize / (vs.Maximum - vs.LargeChange));
            //确保pos的数值lineBytes对齐
            if(pos % lineBytes > 0)
            {
                pos -= pos % lineBytes;
            }
            //最后一页不用部分显示
            while(pos > (fileSize + lineBytes) - (displayLines * lineBytes))
            {
                pos -= lineBytes;
            }
            DisplayFileAtOffset(pos);
        }

        private void HexDisplayClose(object sender, FormClosedEventArgs e)
        {
            CloseFile();
            allHexWin.Remove(this);
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog open = new OpenFileDialog();
            DialogResult res = open.ShowDialog();
            if(res == DialogResult.OK && IsDifferentFile(open.FileName))
            {
                InitNewFile(open.FileName, fs != null);
            }
        }

        private void InitNewFile(string filename, bool newWin)
        {
            try
            {
                FileStream tmpFs = new FileStream(filename, FileMode.Open);
                tmpFs.Dispose();
            }
            catch(System.Exception e)
            {
                MessageBox.Show("打开文件" + filename + "失败!\n" + e.Message);
                return;
            }
            if(newWin)
            {
                HexDisplay newHex = new HexDisplay();
                newHex.InitNewFile(filename, false);
                newHex.Show();
                return;
            }
            else
            {
                CloseFile();
                fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 16 * 1024);
            }
            displayedFile = filename;
            this.Text = displayedFile;
            filePos = 0;
            readSize = 0;
            displayBar.Value = 0;
            fileSize = (int)fs.Length;
            displayBar.Maximum = 1000 + displayBar.LargeChange;
            //文件太小，确保点击滚动至少可以移动一行。
            //小于一行会出现多次点击内容都没有变化
            if(fileSize < lineBytes * 1000)
            {
                displayBar.Maximum = fileSize / lineBytes + displayBar.LargeChange;
            }
            //文件太大，确保点击滚动最多可以移动一页，
            //如果多出一页，会导致部分内容无法查看
            if(fileSize > 1000 * (displayLines * lineBytes))
            {
                displayBar.Maximum = fileSize / (displayLines * lineBytes) + displayBar.LargeChange;
            }
            contextMenu.Items["close"].Available = true;
            contextMenu.Items["jump"].Available = true;
            DisplayFileAtOffset(0);
        }

        private bool IsDifferentFile(string filename)
        {
            if(filename == displayedFile)
            {
                return false;
            }
            FileInfo fi = new FileInfo(filename);
            //目录或设备不处理
            if((fi.Attributes & FileAttributes.Directory) > 0 || (fi.Attributes & FileAttributes.Device) > 0)
            {
                return false;
            }
            return true;
        }

        private void DealMouseWheel(object sender, MouseEventArgs e)
        {
            if(ModifierKeys == Keys.Control)
            {
                return;
            }
            int delta = e.Delta;
            if(delta > 0)
            {
                if(displayBar.Value > 0)
                {
                    --displayBar.Value;
                }
            }
            else if(displayBar.Value < displayBar.Maximum - displayBar.LargeChange)
            {
                ++displayBar.Value;
            }
        }

        private void topMostToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if(TopMost)
            {
                TopMost = false;
                topMost.CheckState = CheckState.Unchecked;
                return;
            }
            foreach(HexDisplay hex in allHexWin)
            {
                if(hex == this)
                {
                    hex.TopMost = true;
                    hex.topMost.CheckState = CheckState.Checked;
                }
                else if(hex.TopMost)
                {
                    hex.TopMost = false;
                    hex.topMost.CheckState = CheckState.Unchecked;
                }
            }
        }

        private string UintToString(uint x)
        {
            if(x >= ' ')
            {
                return "" + (char)x;
            }
            else
            {
                switch(x)
                {
                    case 0x00:
                        return "\\0";
                    case 0x07:
                        return "\\a";
                    case 0x08:
                        return "\\b";
                    case 0x09:
                        return "\\t";
                    case 0x0a:
                        return "\\n";
                    case 0x0b:
                        return "\\v";
                    case 0x0c:
                        return "\\f";
                    case 0x0d:
                        return "\\r";
                    default:
                        return "";
                }
            }
        }

        private void textBox10_TextChanged(object sender, EventArgs e)
        {
            if(haveChange)
            {
                return;
            }
            haveChange = true;
            TextBox tb = (TextBox)sender;
            try
            {
                charValue = uint.Parse(tb.Text);
                str2.Text = ToBinaryString(charValue);
                str16.Text = charValue.ToString("X");
                singleChar.Text = UintToString(charValue);
            }
            catch(System.Exception ex)
            {
                tb.Text = "";
                str2.Text = "";
                str16.Text = "";
                singleChar.Text = "";
            }
            finally
            {
                haveChange = false;
            }
        }

        private void textBox16_TextChanged(object sender, EventArgs e)
        {
            if(haveChange)
            {
                return;
            }
            haveChange = true;
            TextBox tb = (TextBox)sender;
            //确保输入的时候光标在最后面.
            //如果没有这句,输入字母,光标自动回到最前面.
            tb.SelectionStart = tb.Text.Length;
            try
            {
                charValue = uint.Parse(tb.Text, System.Globalization.NumberStyles.AllowHexSpecifier);
                str2.Text = ToBinaryString(charValue);
                str10.Text = charValue.ToString();
                singleChar.Text = UintToString(charValue);
            }
            catch(System.Exception ex)
            {
                tb.Text = "";
                str10.Text = "";
                str2.Text = "";
                singleChar.Text = "";
            }
            finally
            {
                haveChange = false;
            }
        }

        private string ToBinaryString(uint x)
        {
            int i = 0;
            string firstString = "";
            do
            {
                ++i;
                firstString += (x % 2).ToString();
                x >>= 1;
                if(i % 4 == 0)
                {
                    firstString += " ";
                }
            } while(x != 0);
            char[] binaryArray = new char[firstString.Length];
            for(i = 0; i < firstString.Length; i++)
            {
                binaryArray[firstString.Length - 1 - i] = firstString[i];
            }
            return new string(binaryArray);
        }

        private void textBoxChar_TextChanged(object sender, EventArgs e)
        {
            if(haveChange)
            {
                return;
            }
            haveChange = true;
            TextBox tb = (TextBox)sender;
            tb.SelectionStart = Text.Length;
            if(tb.Text.Length > 0)
            {
                if(tb.Text.Length > 1)
                {
                    tb.Text = tb.Text.Substring(1);
                }
                str2.Text = ToBinaryString(tb.Text[0]);
                str10.Text = ((ushort)tb.Text[0]).ToString();
                str16.Text = ((ushort)tb.Text[0]).ToString("X");
            }
            else
            {
                str2.Text = "";
                str10.Text = "";
                str16.Text = "";
            }
            haveChange = false;
        }

        private void hexDisplayArea_SelectionChanged(object sender, EventArgs e)
        {
            if(fs == null || fileSize == 0)
            {
                return;
            }
            //Hex标题行的长度为58字节 == 9 + 16*3 + 1
            int firstLineChars = 58;
            int lineChars = 9 + lineBytes * 3 + 1;
            int hexSelectLength = 1;
            int strSelectLength = 0;
            int prevSelect = 0;
            int currentSelect = 0;
            bool haveStart = false;
            int idx, line, row;
            if(hexDisplayArea.SelectionLength > 1)
            {
                hexSelectLength = hexDisplayArea.SelectionLength;
            }
            for(int i = 0; i < hexSelectLength; i++)
            {
                idx = hexDisplayArea.SelectionStart - firstLineChars + i;
                if(idx >= 0 && idx % lineChars >= 9)
                {
                    line = idx / lineChars;
                    row = (idx % lineChars - 9) / 3;
                    //字符标题行的长度为17字节 == 16 + 1
                    currentSelect = 17 + (lineBytes + 1) * line + row;
                    if(!haveStart)
                    {
                        strDisplayArea.SelectionStart = currentSelect;
                        strSelectLength = 1;
                        haveStart = true;
                    }
                    else if(prevSelect != currentSelect)
                    {
                        ++strSelectLength;
                    }
                    prevSelect = currentSelect;
                }
            }
            strDisplayArea.SelectionLength = strSelectLength;
        }

        private void CloseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CloseFile();
            hexDisplayArea.Text = "";
            strDisplayArea.Text = "";
            this.Text = "16进制";
        }

        private void jumpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if(fs != null && fileSize > 0)
            {
                WinOff offw = new WinOff(this);
                offw.ShowDialog();
            }
        }
    }
}
